//
//  SwizzleSwift.swift
//  SizzleSwift
//
//  Created by Mario on 30/07/2019.
//  Copyright © 2019 Mario Iannotta. All rights reserved.
//
import Foundation


/**
 eg.
 
 need @objc dynamic func
 
 class ViewController: UIViewController {
 
 static let swizzle = Swizzle(ViewController.self) {
 #selector(viewWillAppear(_:)) <-> #selector(swizzleViewWillAppear(_:))
 }
 
 override func viewDidLoad() {
 _ = ViewController.swizzle
 super.viewDidLoad()
 }
 
 override func viewWillAppear(_ animated: Bool) {
 super.viewWillAppear(animated)
 print(#function)
 }
 
 @objc private func swizzleViewWillAppear(_ animated: Bool) {
 print(#function)
 swizzleViewWillAppear(animated)
 }
 }
 */

infix operator <->

public struct SwizzlePair {
    let original: Selector
    let swizzled: Selector
}

public extension Selector {
    
    /// original Selector 必须申明 dynamic 否则无效
    static func <->(original: Selector, swizzled: Selector) -> SwizzlePair {
        SwizzlePair(original: original, swizzled: swizzled)
    }
}

@resultBuilder
public class ResultBuilder<T> {
    public static func buildBlock(_ components: T...) -> [T] {
        components.compactMap { $0 }
    }
}

public struct Swizzle {
    
    @discardableResult
    public init(_ type: AnyObject.Type, @ResultBuilder<SwizzlePair> _ makeSwizzlePairs: () -> [SwizzlePair]) {
        swizzle(type: type, pairs: makeSwizzlePairs())
    }
    
    private func swizzle(type: AnyObject.Type, pairs: [SwizzlePair]) {
        pairs.forEach { swizzlePair in
            swizzleMethod(for: type,
                             originalSelector: swizzlePair.original,
                             swizzledSelector: swizzlePair.swizzled)
        }
    }
    
}

public func swizzleMethod(for aClass: AnyClass,
                          originalSelector: Selector,
                          swizzledSelector: Selector) {
    
    guard let originalMethod = class_getInstanceMethod(aClass, originalSelector),
          let swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector) else {
              return
          }
    
    let didAddMethod = class_addMethod(
        aClass,
        originalSelector,
        method_getImplementation(swizzledMethod),
        method_getTypeEncoding(swizzledMethod)
    )
    
    if didAddMethod {
        class_replaceMethod(
            aClass,
            swizzledSelector,
            method_getImplementation(originalMethod),
            method_getTypeEncoding(originalMethod)
        )
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

